package collector

import (
	"context"
	"database/sql"

	_ "gitee.com/kvenli/dm_exporter/dm"

	"log/slog"
	"sync"
	"time"

	"github.com/prometheus/client_golang/prometheus"
)

var (
	dbConnections = make(map[string]*sql.DB)
	dbMutex       = &sync.Mutex{}
)

// Metric name parts.
const (

	// Exporter namespace.
	namespace string = "dm"
	// Subsystem(s).
	exporter = "exporter"
)

// SQL queries and parameters.

// Metric descriptors.
var (
	scrapeDurationDesc = prometheus.NewDesc(
		prometheus.BuildFQName(namespace, exporter, "collector_duration_seconds"),
		"Collector time duration.",
		[]string{"collector"}, nil,
	)
)

var (
	name string = "dm_exporter"
)

// fqname会把namespace subsystem name拼接起来
// 传入动态以及静态标签 设置标签
//
//	func newDesc(subsystem, name, help string) *prometheus.Desc {
//		return prometheus.NewDesc(
//			prometheus.BuildFQName(namespace, subsystem, name),
//			help, nil, nil,
//		)
//	}
func Name() string {
	return name
}

// 验证Exporter是否实现了prometheus.Collector这个接口
var _ prometheus.Collector = (*Exporter)(nil)

type Exporter struct {
	ctx        context.Context
	metrics    Metrics
	scrapers   []Scraper
	dsn        string
	backupPath string
	logger     *slog.Logger
}

// func newDesc(subsystem, name, help string, variablelabels []string, labels prometheus.Labels) *prometheus.Desc {
// 	return prometheus.NewDesc(
// 		prometheus.BuildFQName(namespace, subsystem, name), //fqName
// 		help,
// 		variablelabels,
// 		labels,
// 	)
// }

func New(ctx context.Context, dsn string, backupPath string, metrics Metrics, scrapers []Scraper, logger *slog.Logger) *Exporter {
	return &Exporter{
		ctx:        ctx,
		dsn:        dsn,
		backupPath: backupPath,
		metrics:    metrics,
		scrapers:   scrapers,
		logger:     logger,
	}
}

// 添加描述符
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
	ch <- e.metrics.TotalScrapes.Desc()
	ch <- e.metrics.Error.Desc()
	e.metrics.ScrapeErrors.Describe(ch)
	e.metrics.DMUp.Describe(ch)
}

// 收集指标
func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
	e.scrape(ch)
	ch <- e.metrics.TotalScrapes
	ch <- e.metrics.Error
	e.metrics.ScrapeErrors.Collect(ch)
	e.metrics.DMUp.Collect(ch)

}

// 通过例程并发的收集指标 需要加waitgroup
func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
	e.metrics.TotalScrapes.Inc()
	var (
		wg  sync.WaitGroup
		err error
	)
	scrapeTime := time.Now()

	db, err := getDBConnection(e.dsn)
	if err != nil {
		e.logger.Error("Error opening connection to database", "error", err)
		e.metrics.Error.Set(1)
		return
	}

	if err := db.Ping(); err != nil {
		e.logger.Error("Error pinging dm8", "error", err)
		e.metrics.DMUp.Set(0)
		e.metrics.Error.Set(1)
		return
	}

	//获取dm_info信息

	e.metrics.DMUp.Set(1)
	e.metrics.Error.Set(0)

	ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "connection")

	defer wg.Wait()
	for _, scraper := range e.scrapers {
		wg.Add(1)
		go func(scraper Scraper) {
			defer wg.Done()
			label := scraper.Name()
			scrapeTime := time.Now()

			// Create a new context with a timeout for each scraper.
			// Use half of the total scrape timeout, but no more than 10 seconds.
			timeout := 10 * time.Second
			if deadline, ok := e.ctx.Deadline(); ok {
				// Use half of the remaining time.
				timeout = time.Until(deadline) / 2
			}
			if timeout > 10*time.Second {
				timeout = 10 * time.Second
			}
			ctx, cancel := context.WithTimeout(context.Background(), timeout)
			defer cancel()

			// Run the scrape in a separate goroutine to handle timeouts.
			errChan := make(chan error, 1)
			go func() {
				errChan <- scraper.Scrape(db, ch, e.backupPath, e.logger)
			}()

			select {
			case err := <-errChan:
				if err != nil {
					e.logger.Error("Error scraping for scraper", "scraper", scraper.Name(), "error", err)
					e.metrics.ScrapeErrors.WithLabelValues(label).Inc()
					e.metrics.Error.Set(1)
				}
			case <-ctx.Done():
				e.logger.Error("Timeout scraping for scraper", "scraper", scraper.Name(), "error", ctx.Err())
				e.metrics.ScrapeErrors.WithLabelValues(label).Inc()
				e.metrics.Error.Set(1)
			}

			ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), label)
		}(scraper)
	}
}

func getDBConnection(dsn string) (*sql.DB, error) {
	dbMutex.Lock()
	defer dbMutex.Unlock()

	if db, ok := dbConnections[dsn]; ok {
		return db, nil
	}

	db, err := sql.Open("dm", dsn)
	if err != nil {
		return nil, err
	}
	db.SetMaxOpenConns(1)
	db.SetMaxIdleConns(1)
	db.SetConnMaxLifetime(1 * time.Minute)

	dbConnections[dsn] = db
	return db, nil
}

/*
func getDMVersion(db *sql.DB, logger log.Logger) float64 {
	var versionStr string
	var versionNum float64
	if err := db.QueryRow(versionQuery).Scan(&versionStr); err == nil {
		versionNum, _ = strconv.ParseFloat(versionStr, 64)
	} else {
		logger.Debug("msg", "Error querying version", "err", err)
	}
	// If we can't match/parse the version, set it some big value that matches all versions.
	if versionNum == 0 {
		logger.Info("msg", "Error parsing version string", "version", versionStr)
		versionNum = 999
	}
	return versionNum
}
*/

type Metrics struct {
	//收集指标的总次数
	TotalScrapes prometheus.Counter
	//收集指标中发生错误的次数
	ScrapeErrors *prometheus.CounterVec
	//最后一次是否发生了错误
	Error prometheus.Gauge
	//DMUp prometheus.Gauge
	DMUp prometheus.Gauge
}

// NewMetrics creates new Metrics instance.
func NewMetrics() Metrics {
	subsystem := exporter
	return Metrics{
		TotalScrapes: prometheus.NewCounter(prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: subsystem,
			Name:      "scrapes_total",
			Help:      "Total number of times  was scraped for metrics.",
		}),
		ScrapeErrors: prometheus.NewCounterVec(prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: subsystem,
			Name:      "scrape_errors_total",
			Help:      "Total number of times an error occurred scraping .",
		}, []string{"collector"}),
		Error: prometheus.NewGauge(prometheus.GaugeOpts{
			Namespace: namespace,
			Subsystem: subsystem,
			Name:      "last_scrape_error",
			Help:      "Whether the last scrape of metrics  resulted in an error (1 for error, 0 for success).",
		}),
		DMUp: prometheus.NewGauge(prometheus.GaugeOpts{
			Namespace: namespace,
			Name:      "up",
			Help:      "Whether the DM server is up.",
		}),
	}
}
